﻿// Copyright 2013 Ronald Schlenker, www.technewlogic.de.
// 
// This file is part of Byond.
// 
// Byond is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 2 of the License, or
// (at your option) any later version.
// 
// Byond is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with Byond.  If not, see <http://www.gnu.org/licenses/>.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using Byond.Compilation;
using Byond.HostObjects;
using Byond.Logging;
using Byond.PatchUiControls;
using Byond.Persistence;
using Byond.UIRuntime;
using Utils.Messaging;

namespace Byond
{
	public sealed class PatchManager
	{
		private readonly IEnsembleQueryService _ensembleQueryService;
		private readonly UiManager _uiManager;
		private readonly Lazy<VoiceManager> _voiceManager;
		private readonly Lazy<IUserLogger> _logger;
		private readonly PatchCompiler _currentPatchCompiler;

		internal PatchManager()
		{
			RootPatch = Patch.Empty;
			RootPatchControl = new Control();
		}

		public event MessageEventHandler SendMessage;

		public Patch RootPatch { get; private set; }
		public Ensemble RootEnsemble { get; private set; }
		public Control RootPatchControl { get; private set; }

		private readonly List<MetaPatch> _patches = new List<MetaPatch>();
		internal IEnumerable<MetaPatch> Patches { get { return _patches; } }

		public void CreatePatch(Ensemble ensemble)
		{
			var patch = _currentPatchCompiler.GeneratePatch(ensemble);
			var metaPatch = _patches.First(it => it.Patch == patch);
			var patchControl = CreatePatchControl(metaPatch);

			// initialize the patch panels
			var i = 0;
			SetupPatchPanels(metaPatch, ref i);

			RootPatch = patch;
			RootPatchControl = patchControl;
			RootEnsemble = ensemble;

			SetupBindings();
		}

		public void RunCreatedPatch()
		{
			// Die Reihenfolge war vorher umgedreht...
			ExecuteGoCode();
			
			// TODO: Reinmachen
			//Restore(null);

			_voiceManager.Value.EnableVoiceProcessing = true;
		}

		public void RegisterPatch(Patch patch, Patch parent, string memberName)
		{
			var parentMetaPatch = (parent != null)
				? _patches.First(it => it.Patch == parent)
				: null;
			var metaPatch = new MetaPatch(patch, memberName, parentMetaPatch);
			_patches.Add(metaPatch);
		}

		private void SetupPatchPanels(MetaPatch metaPatch, ref int i)
		{
			i++;
			if (i > 100)
				throw new Exception("Maximum patch panel nesting reached (circular patch panels?).");

			var patchPanels = metaPatch.PatchControl
				.GetLogicalDescendants()
				.OfType<PatchPanel>()
				.Where(it => it.PatchControl == null)
				.ToList();
			foreach (var it in patchPanels)
			{
				var childPatch = metaPatch.FindDescendantByPath<MetaPatch>(it.Path);
				childPatch.PatchControl = CreatePatchControl(childPatch);
				it.PatchControl = childPatch.PatchControl;

				SetupPatchPanels(childPatch, ref i);
			}
		}

		private void ExecuteGoCode()
		{
			// Reverse: Gibt's auch bei List, deshlab Downcase auf Enumerable
			foreach (var patch in _patches.AsEnumerable().Reverse())
			{
				_logger.Value.Log("Init: " + patch.Patch.GetType().Name);
				patch.Patch.Go();
			}
		}

		private Control CreatePatchControl(MetaPatch metaPatch)
		{
			var ensemble = _ensembleQueryService.GetByFileName(metaPatch.EnsembleFileName);
			var patchControl = !string.IsNullOrWhiteSpace(ensemble.UiCode)
				? _currentPatchCompiler.GenerateUi(ensemble.UiCode)
				: new Control();
			metaPatch.PatchControl = patchControl;
			return patchControl;
		}

		private void SetupBindings()
		{
			// Performance hit
			var logicalDescendants = _patches
				.Select(it => new
					{
						Patch = it,
						Descendents = it.PatchControl.GetLogicalDescendants(true)
					})
				.ToList();

			// hook up the bindings
			var potentialBindings = UiBindingMarkupExtension
				.GetRegistrations()
				.Select(it => new
					{
						MarkupExtension = it,
						Patch = logicalDescendants.FirstOrDefault(it2 => it2.Descendents.Contains((DependencyObject)it.Parent))

						// vorher: ohne cache
						//Patch = _patches.FirstOrDefault(it2 =>
						//    it2.PatchControl.GetLogicalDescendants(true).Contains((DependencyObject)it.Parent))
					})
				.Where(it => it.Patch != null)
				.ToList();
			
			var bindingFields = new List<FieldAndPropWrapper>();
			foreach (var it in potentialBindings)
			{
				// create the binder...
				var codeBehindProperty = GetCodeBehindBindingTarget(it.MarkupExtension.BindingPath, it.Patch.Patch);
				var uiControlBindingTarget = new FrontendBindingEndpoint(it.MarkupExtension.Parent, it.MarkupExtension.Property);

				int? index;
				string s;
				it.MarkupExtension.BindingPath.TryGetBindingIndex(out index, out s);
				var bindingProperty = new Binding(uiControlBindingTarget, codeBehindProperty, index);
				_uiManager.RegisterBinding(bindingProperty);

				// which binding targets do we have?
				bindingFields.AddRange(codeBehindProperty.Targets);
			}

			// mark the property as bound in the meta patch
			foreach (var it in _patches)
				it.RegisterBoundProperties(bindingFields);

			// finally, remove the registrations
			UiBindingMarkupExtension.RemoveRegistration(
				potentialBindings.Select(it => it.MarkupExtension).ToList());
		}

		private static BackendBindingEndpoint GetCodeBehindBindingTarget(string bindingPath, MetaPatch patch)
		{
			// TODO: Evtl. Multi-Binding machen?

			return new BackendBindingEndpoint(
				bindingPath
					.Split(new[] { ";" }, StringSplitOptions.RemoveEmptyEntries)
					.Select(it =>
						{
							var fieldAndPropWrapper = patch.FindDescendantByPath<FieldAndPropWrapper>(it);
							var couldCreateBinding = fieldAndPropWrapper != null;
							return new
								{
									// TODO: Logging, wenn couldCreateBinding == false
									CouldCreateBinding = couldCreateBinding,
									Binding = fieldAndPropWrapper
								};
						})
					.Where(it => it.CouldCreateBinding)
					.Select(it => it.Binding)
					.ToList());
		}

		public void RestoreState(IEnumerable<PatchDto> patchDtos)
		{
			var propsToMap = (
				from dto in patchDtos
				let metaPatch = _patches.FirstOrDefault(it => it.RootPath == dto.RootPath)
				where metaPatch != null
				from metaProp in metaPatch.PersistProperties
				let dtoProp = dto.Properties.FirstOrDefault(it => it.Name == metaProp.MemberName)
				where dtoProp != null
				select new
					{
						metaProp,
						dtoProp,
						metaPatch
					}
				).ToList();

			foreach (var it in propsToMap)
			{
				try
				{
					it.metaProp.SetValueMethod(it.dtoProp.Value, null);
				}
				catch (Exception e)
				{
					_logger.Value.Log("ERROR: Could not restore property: " + it.dtoProp.Name + " / " +
						it.dtoProp.Value + ": " + e.Message);
				}
			}
		}
	}
}